/***************************************************************
*  Copyright (c) 2007 by GroupMe! Team (www.groupme.net)
*  All rights reserved.
*
*  This file is part of the GroupMe! Project. Source code of 
*  this project is closed and redistribution of this code is
*  prohibited. 
*  
*  Contact: http://www.groupme.net
*
*  This copyright notice MUST APPEAR in all copies of the file!
***************************************************************/
package net.groupme.controller.ajax;

import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;

import net.groupme.controller.model.GroupMeSearchResult;
import net.groupme.controller.model.HtmlFormattedElement;
import net.groupme.controller.model.SearchResult;
import net.groupme.exceptions.DeleteFailedException;
import net.groupme.exceptions.GameNotFoundException;
import net.groupme.exceptions.GroupVisualizationException;
import net.groupme.exceptions.InsertFailedException;
import net.groupme.exceptions.UpdateFailedException;
import net.groupme.game.GAME_CONSTANTS;
import net.groupme.game.GameManager;
import net.groupme.game.GroupMeGame;
import net.groupme.group.GroupManager;
import net.groupme.gui.constants.VIEW_VOCABULARY;
import net.groupme.gui.rendering.Renderer;
import net.groupme.gui.rendering.RenderingUtility;
import net.groupme.gui.rendering.TagRenderer;
import net.groupme.model.Group;
import net.groupme.model.ObjectFactory;
import net.groupme.model.Resource;
import net.groupme.model.ResourceVisualization;
import net.groupme.model.Tag;
import net.groupme.model.User;
import net.groupme.model.dao.mem.ResourceVisualizationDAOMemImpl;
import net.groupme.search.strategy.model.WeightedTag;
import net.groupme.utils.StringUtilities;
import net.groupme.utils.TagCloudComputation;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

/**
 * created on 22.02.2008
 * 
 * This class/interface...
 * 
 * @author Mischa Frank, <a href="mailto:code@planetfrank.net">code@planetfrank.net</a>
 * @author last edited by: $Author: fabian $
 * 
 * @version $Revision: 1.10 $ $Date: 2008-02-29 15:01:28 $
 */
public class GameAjaxController implements ApplicationContextAware {

	/** Logger for this class and subclasses */
	protected final Log logger = LogFactory.getLog(getClass());
	
	/** the Spring application context */
	private ApplicationContext applicationContext = null;
	
	/* (non-Javadoc)
	 * @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context.ApplicationContext)
	 */
	public void setApplicationContext(ApplicationContext context) throws BeansException {
		this.applicationContext = context;
	}
	
	/**
	 * This method returns an array of the games that are queued (where users are
	 * waiting for the second player)
	 * @return an array of the games that are queued (where users are
	 * waiting for the second player)
	 */
	public GroupMeGame[] getPendingGames(){
		return GameManager.getInstance().getGamesQueued().toArray(new GroupMeGame[0]);
	}
	public int getScore(String gameId){
		//logger.info("Computing Score for game with id: " +gameId);
		GroupMeGame game;
		int score = 0;
		try {
			if (GameManager.getInstance().isGameRunning(new Long(gameId))){
				game = GameManager.getInstance().getGameRunning(new Long(gameId));
			}
			else {
				game = GameManager.getInstance().getGameFinished(new Long(gameId));
			}
			score = game.getOverallScore();
		} catch (NumberFormatException e) {
			logger.error(e.getMessage());
			e.printStackTrace();
		} catch (GameNotFoundException e) {
			logger.error(e.getMessage());
			e.printStackTrace();
		}
		return score;
	}
	
	/**
	 * This method checks if a game with the given id is in the running list.
	 * @param gameId
	 * @return true if the game is running, otherwise false
	 */
	public boolean isGameRunning(String gameId){
		logger.info("Checking if game with id: " + gameId + " is running..");
		return GameManager.getInstance().isGameRunning(new Long(gameId));
	}
	
	/**
	 * Adds the Tag with the given Keyword to the Resource with the given ID.
	 * @param gameId the id of the game
	 * @param player the player (creator or opponent)
	 * @param keyword the Keyword of the Tag
	 * @param resourceID the ID of the Resource which should be tagged
	 * @return true if successful, otherwise false
	 */
	public boolean addTagToResource(Long gameId, String player, String keywords, String resourceID){
		logger.info("Adding tags: " + keywords + " to resource: " +resourceID);
		try {
			List<Tag> tags = getTags(keywords);
			Resource resource = getResource(player, gameId, resourceID);
			Group group = getGroup(player, gameId);
			User user = getUser(player, gameId);
			for (Tag tag : tags) {
				resource.addTag(user, tag, group);
			}
			return true;
		} catch (InsertFailedException e) {
			logger.error(e.getMessage());
			e.printStackTrace();
			return false;
		} catch (GameNotFoundException e) {
			logger.error(e.getMessage());
			e.printStackTrace();
			return false;
		}
	}
	
	
	/**
	 * Adds the Tag with the given Keyword to the Group the player is building.
	 * @param gameId the game
	 * @param player the player who added the tag
	 * @param keywords the keyword string (tags are separated via comma)
	 */
	public boolean addTagToGroup(Long gameId, String player, String keywords){
		logger.info("Adding tags: " + keywords + " to the group of player " + player);
		try {
			List<Tag> tags = getTags(keywords);
			Group group = getGroup(player, gameId);
			User user = getUser(player, gameId);
			for (Tag tag : tags) {
				group.addTag(user, tag);
			}
			return true;
		} catch (InsertFailedException e) {
			logger.error(e.getMessage());
			e.printStackTrace();
			return false;
		} catch (GameNotFoundException e) {
			logger.error(e.getMessage());
			e.printStackTrace();
			return false;
		}
	}
	
	/**
	 * Removes a given ResourceVisualization and its linked Resource from the GroupVisualization
	 * and the Group by redirecting it to the {@link GroupManager}.
	 * @param resVisuID the ID of the ResourceVisualization
	 * @return <code>true</code> if removing was successfull, otherwise <code>false</code>
	 */
	public boolean removeResourceFromGroup(String player, Long gameId, String resVisuID){
		Group group;
		try {
			group = this.getGroup(player, gameId);
			ResourceVisualization resVisu = this.getResourceVisualization(gameId, player, resVisuID);
			group.removeResource(resVisu.getResource());
			group.getActualGroupVisualization("").removeResourceVisualization(resVisu);
		} catch (GameNotFoundException e) {
			logger.error(e.getMessage());
			e.printStackTrace();
			return false;
		} catch (GroupVisualizationException e) {
			logger.error(e.getMessage());
			e.printStackTrace();
			return false;
		} catch (DeleteFailedException e) {
			logger.error(e.getMessage());
			e.printStackTrace();
			return false;
		}
		return true;
	}
	
	/**
	 * Returns the name of an Resource by a corresponding ResourceVisualization
	 * @param player Creator or Opponent
	 * @param gameId the Id of the current game
	 * @param resVisuID the ID of an corresponding ResourceVisualization
	 * @return the name of the Resource as String
	 */
	public String getResourceName(String player, Long gameId, String resVisuID){
		try {
			return this.getResourceVisualization(gameId, player, resVisuID).getResource().getName();
		} catch (GameNotFoundException e) {
			logger.error(e.getMessage());
			e.printStackTrace();
			return "#Error";
		} catch (GroupVisualizationException e) {
			logger.error(e.getMessage());
			e.printStackTrace();
			return "#Error";
		}
	}
	
	/**
	 * Returns the corresponding Tags (in memory with correct ID)
	 * @param keyword the tag as string
	 * @return the corresponding Tags (in memory with correct ID)
	 */
	private List<Tag> getTags(String keywords){
		keywords = keywords.toLowerCase().trim();
		Collection<String> tagsAsString = StringUtilities.extractTags(keywords, false);
		List<Tag> tags = new ArrayList<Tag>();
		Tag tagDAO = null;
		for (String keyword : tagsAsString) {
			try {
				tagDAO = ObjectFactory.getTagForKeyword(keyword);
			} catch (InsertFailedException e) {
				e.printStackTrace();
				logger.error("Unable to get tag..." + e.getMessage());
				break;
			}
			tags.add(ObjectFactory.getInMemoryTag(tagDAO.getId(), keyword, ""));
		}
		return tags;
	}
	/**
	 * Returns the group of either the creator or the opponent.
	 * @param player either {@link GAME_CONSTANTS#PLAYER_CREATOR} or {@link GAME_CONSTANTS#PLAYER_OPPONENT}
	 * @param gameId the id of the game
	 * @return the group of either the creator or the opponent.
	 * @throws GameNotFoundException game not found
	 */
	private User getUser(String player, Long gameId) throws GameNotFoundException{
		GroupMeGame game = GameManager.getInstance().getGameRunning(gameId);
		if (player.equals(GAME_CONSTANTS.PLAYER_CREATOR)){
			return game.getCreator();
		}
		else {
			return game.getOpponent();
		}
	}
	/**
	 * Returns the group of either the creator or the opponent.
	 * @param player either {@link GAME_CONSTANTS#PLAYER_CREATOR} or {@link GAME_CONSTANTS#PLAYER_OPPONENT}
	 * @param gameId the id of the game
	 * @return the group of either the creator or the opponent.
	 * @throws GameNotFoundException game not found
	 */
	private Group getGroup(String player, Long gameId) throws GameNotFoundException{
		GroupMeGame game = GameManager.getInstance().getGameRunning(gameId);
		if (player.equals(GAME_CONSTANTS.PLAYER_CREATOR)){
			return game.getCreatorsGroup();
		}
		else {
			return game.getOpponentsGroup();
		}
	}
	/**
	 * Returns the resource that corresponds to the given ID.
	 * @param player either {@link GAME_CONSTANTS#PLAYER_CREATOR} or {@link GAME_CONSTANTS#PLAYER_OPPONENT}
	 * @param gameId the id of the game
	 * @param resourceID the resource's ID
	 * @return the corresponding resource or <code>null</code>
	 * @throws GameNotFoundException 
	 */
	private Resource getResource(String player, Long gameId, String resourceID) throws GameNotFoundException{
		Collection<Resource> resources = getGroup(player, gameId).getResources();
		for (Resource resource : resources) {
			if(resource.getId().equals(resourceID)){
				return resource;
			}
		}
		return null;
	}
	/**
	 * This method loads all Tags for the given Resource, the User logged in
	 * and the Group that is shown at the moment. It also renders the Tags by 
	 * a suiting renderer.
	 * @param resourceID the ID of the tagged Resource
	 * @return an array of the HTML formatted Tags
	 */
	public HtmlFormattedElement[] loadTagsForResource(Long gameId, String player, String resourceID){
		Collection<Tag> tags = null;
		logger.info("loading tags for player '" + player + "' and resource " + resourceID);
		try {
			tags = getResource(player, gameId, resourceID).getTags();
		} catch (GameNotFoundException e) {
			e.printStackTrace();
			logger.error(e);
			return new HtmlFormattedElement[0];
		}
		return renderTags(tags);
	}
	
	/**
	 * This method loads tags for the actual group. It also renders the Tags by 
	 * a suiting renderer.
	 * @param allTags  if this parameter is <code>false</code>, then we only search for tags that were
	 * inserted by the actual user, otherwise (if this parameter is <code>true</code>) all tags are
	 * considered.
	 * 
	 * @return an array of the HTML formatted Tags
	 */
	public HtmlFormattedElement[] loadTagsForGroup(Long gameId, String player, boolean allTags){
		Collection<Tag> tags = null;
		try {
			tags = getGroup(player, gameId).getTags();
		} catch (GameNotFoundException e) {
			e.printStackTrace();
			logger.error(e);
		}
		return renderTags(tags);
	}
	
	/**
	 * Renders the given collection of tags...
	 * @param tags the tags to render
	 * @return an array of the HTML formatted Tags
	 */
	private HtmlFormattedElement[] renderTags(Collection<Tag> tags){
		HtmlFormattedElement[] formattedElements = new HtmlFormattedElement[tags.size()];
		int position = 0;
		for (Tag tag : tags) {
			formattedElements[position] = renderTag(tag);
			position++;
		}
		return formattedElements;
	}
	/**
	 * Renders a given tag by creating a hyperlink which causes invocation of the internal GroupMe! search engine
	 * (searching for groups and resources).
	 * @param tag the tag to format as HTML
	 * @return HTML-formatted {@link Tag}
	 */
	private HtmlFormattedElement renderTag(Tag tag){
		String tagLink = "<a href=\"" + VIEW_VOCABULARY.GROUPME_BASE_URL + VIEW_VOCABULARY.ACTION_SEARCH + "?" + VIEW_VOCABULARY.PARAMETER_KEYWORD 
			+ "=" + tag.getKeyword() +"\" title=\"Search for Groups and Resources tagged with " + 
			tag.getKeyword() +"\">" + tag.getKeyword() +"</a>";
		return new HtmlFormattedElement(tag.getId(), tagLink);
	}
	
	/**
	 * This method adds a <i>Search Result</i> to a Group. Hence, the SearchResult has to
	 * be transformed into a GroupMe! {@link Resource} instance (Remember that 
	 * resources can also be a <i>Search Result</i>). The transformation process of
	 * new resources includes also metadata extraction.
	 * 
	 * After that the resource will be added to the Group and rendered by a suiting {@link Renderer}.
	 *  
	 * @param searchResultID the ID of the search result
	 * @return the HTML representation of the (created) resource
	 */
	public HtmlFormattedElement addSearchResultToGroup(String player, long gameID, String searchResultID, Integer posX, Integer posY, Integer resWidth, Integer resHeight, Integer zAxis, Integer groupWidth, Integer groupHeight){
		GroupMeGame game;
		HtmlFormattedElement result = new HtmlFormattedElement();
		try {
			game = GameManager.getInstance().getGameRunning(gameID);
			SearchResult resourceToAdd = game.getSearchResultForID(searchResultID);
		
			//1. create resource 
			Resource resource = ObjectFactory.getInMemoryResourceWithValidId(resourceToAdd.getName(), resourceToAdd.getDescription(), new URL(resourceToAdd.getUrl()), resourceToAdd.getType());
			ResourceVisualization resVisu = ObjectFactory.getInMemoryResourceVisualizationWithValidId(game.getGroupByRole(player).getStandardVisualization(), resource, posX, posY, resWidth, resHeight);
			resVisu.setResource(resource); 
			resVisu.setZAxis(zAxis);
			
			//2. add Resource to correct group
			game.getGroupByRole(player).addResource(resource);

			//3. detect suiting renderer:
			if(resourceToAdd instanceof GroupMeSearchResult){
				resVisu.setRenderer(RenderingUtility.suggestRenderer(((GroupMeSearchResult)resourceToAdd).getResource().getType()));
			}else{
				resVisu.setRenderer(RenderingUtility.suggestRenderer(resourceToAdd.getType()));
			}
			//3a. add ResourceVisualization to correct GroupVisualization
			try {
				game.getGroupByRole(player).getActualGroupVisualization("").addResourceVisualization(resVisu);
			} catch (GroupVisualizationException e) {
				logger.error(e.getMessage());
				e.printStackTrace();
			}
			
			//4. let Spring create the corresponding Renderer and then return the resourceVisualization in an HTML format
			Renderer resourceRenderer = (Renderer) applicationContext.getBean(resVisu.getRenderer(), Renderer.class);
			result = new HtmlFormattedElement(resVisu.getURI().toString(), resourceRenderer.renderAsHTML(resVisu, 500, 600, 100));
			
			
			//5. save Position and Size
			resHeight = new Double(resHeight*1.9).intValue();
			updateResourceVisualization(gameID, player, resVisu.getURI().toString(), posX, posY, resWidth, resHeight, zAxis, groupWidth, groupHeight);
			
		} catch (Exception e) {
			e.printStackTrace();
			logger.error(e);
		} 
		return result;
	}
	
	/**
	 * This method updates the position and the size of a ResourceVisualization relativly to the
	 * width of the Group.
	 * @param resVisuID the ID of the ResourceVisualization which should be updated
	 * @param posX the new X-position
	 * @param posY the new Y-position
	 * @param resWidth the new width of the ResourceVisualization
	 * @param resHeight the new height of the ResourceVisualization
	 * @param groupWidth the Width of the Group
	 * @param groupHeight the Height of the Group
	 * @return true if the update was successdul, otherwise false
	 */
	public boolean updateResourceVisualization(long gameID, String player, String resVisuURI, Integer posX, Integer posY, Integer resWidth, Integer resHeight, Integer zAxis, Integer groupWidth, Integer groupHeight){
		try {
			ResourceVisualization resVisu = getResourceVisualization(gameID, player, resVisuURI);
			resVisu.setPositionX((posX*1000)/groupWidth);
			resVisu.setPositionY((posY*1000)/groupHeight);
			resVisu.setWidth((resWidth*1000)/groupWidth);
			resVisu.setHeight((resHeight*1000)/groupHeight);
			resVisu.setZAxis(zAxis);
			logger.info("Updated ResourceVisualization with ID: " + resVisuURI + ".");
			logger.info("Set posX: "+posX+", posY: "+posY+", width: "+resWidth+", height: "+resHeight+", zAxis: "+zAxis+" " );
		} catch (UpdateFailedException e) {
			logger.error(e.getMessage());
			e.printStackTrace();
			return false;
		} catch (GroupVisualizationException e) {
			logger.error(e.getMessage());
			e.printStackTrace();
			return false;
		} catch (GameNotFoundException e) {
			logger.error(e.getMessage());
			e.printStackTrace();
			return false;
		}
		return true;
	}

	/**
	 * @param gameID
	 * @param player
	 * @param resVisuURI
	 * @return
	 * @throws GameNotFoundException
	 * @throws GroupVisualizationException
	 */
	private ResourceVisualization getResourceVisualization(long gameID, String player, String resVisuURI) throws GameNotFoundException, GroupVisualizationException {
		GroupMeGame game = GameManager.getInstance().getGameRunning(gameID);
		ResourceVisualization resVisu = new ResourceVisualizationDAOMemImpl(0, 0, 0, 0, "0");
		Collection<ResourceVisualization> resVisus = new ArrayList<ResourceVisualization>();
		if (GAME_CONSTANTS.PLAYER_CREATOR.equals(player)){
			resVisus = game.getCreatorsGroup().getActualGroupVisualization("").getResourceVisualizations();
		}
		else if (GAME_CONSTANTS.PLAYER_OPPONENT.equals(player)){
			resVisus = game.getOpponentsGroup().getActualGroupVisualization("").getResourceVisualizations();
		}
		Iterator<ResourceVisualization> iter = resVisus.iterator();
		while (iter.hasNext()) {
			ResourceVisualization element = (ResourceVisualization) iter.next();
			if (element.getURI().toString().equals(resVisuURI)) {
				resVisu = element;
				logger.info("Found ResourceVisualization with URI: " + resVisu.getURI().toString());
				break;
			}
		}
		return resVisu;
	}
	
	/**
	 * This method loads all Resources of the Group and renders it by a suiting Renderer.
	 * @param groupWith the width of the current group
	 * @return an Array of the formatted Resources
	 */
	public HtmlFormattedElement[] loadResources(long gameID, String player, Integer groupWidth, Integer groupHeight){
		ResourceVisualization[] groupResources = new ResourceVisualization[0];;
		if (GAME_CONSTANTS.PLAYER_CREATOR.equals(player)){
			try {
				groupResources = GameManager.getInstance().getGameRunning(gameID).getCreatorsGroup().getActualGroupVisualization("")
															.getResourceVisualizations().toArray(new ResourceVisualization[0]);
			} catch (GroupVisualizationException e) {
				logger.error(e.getMessage());
				e.printStackTrace();
			} catch (GameNotFoundException e) {
				logger.error(e.getMessage());
				e.printStackTrace();
			}
		}
		else if (GAME_CONSTANTS.PLAYER_OPPONENT.equals(player)){
			try {
				groupResources = GameManager.getInstance().getGameRunning(gameID).getOpponentsGroup().getActualGroupVisualization("")
															.getResourceVisualizations().toArray(new ResourceVisualization[0]);
			} catch (GroupVisualizationException e) {
				logger.error(e.getMessage());
				e.printStackTrace();
			} catch (GameNotFoundException e) {
				logger.error(e.getMessage());
				e.printStackTrace();
			}
		}
		else {
			logger.error("Player could not be correctly identified!");
		}
		HtmlFormattedElement[] formattedGroupResources = new HtmlFormattedElement[groupResources.length];
		
		// sortieren nach zAxis
		Arrays.sort(groupResources, new ResourceVisuComparatorZAxis());
		int zAxis = 10;
		for (int i = 0; i < groupResources.length; i++) {
			try {
				groupResources[i].setZAxis(zAxis);
			} catch (UpdateFailedException e) {
				logger.debug("Update of zAxis failed.");
			}
			Renderer resourceRenderer = (Renderer) applicationContext.getBean(groupResources[i].getRenderer(), Renderer.class);
			formattedGroupResources[i] = new HtmlFormattedElement(groupResources[i].getURI().toString(), resourceRenderer.renderAsHTML(groupResources[i], groupWidth, groupHeight, zAxis));
			zAxis++;
		}
		return formattedGroupResources;
	}
	
	
	/**
	 * This method returns the tag cloud of a group. 
	 *  
	 * @param gameId the id of the game
	 * @param player either {@link GAME_CONSTANTS#PLAYER_CREATOR} or {@link GAME_CONSTANTS#PLAYER_OPPONENT}
	 * @param groupID the ID of the group for which the tag cloud should be computed 
	 * @return an HTML-formatted tag cloud of the given group
	 */
	public HtmlFormattedElement getGroupTagCloudByID(long gameID, String player, String groupID){
		logger.info("Starting to compute tag cloud for: " + groupID);
		Group group;
		try {
			group = GameManager.getInstance().getGameByID(gameID).getGroupByRole(player);
		} catch (GameNotFoundException e) {
			e.printStackTrace();
			logger.error(e);
			return new HtmlFormattedElement(null, "unable to compute tag cloud");
		}
		
		List<WeightedTag> weightedTags = TagCloudComputation.computeTagCloud(group);
		HtmlFormattedElement tagCloud = new HtmlFormattedElement(null, new TagRenderer().renderTagCloud(weightedTags));
		return tagCloud;
	}
	
	
	private class ResourceVisuComparatorZAxis implements Comparator {
		public int compare(Object arg0, Object arg1) {
			int z1 = ((ResourceVisualization) arg0).getZAxis();
			int z2 = ((ResourceVisualization) arg1).getZAxis();
			if (z1 < z2) return -1;
			if (z1 == z2) return 0;
			else return 1;
		}
	}
}
